package com.iflytek.sbd.utils;


import com.iflytek.sbd.exception.ServiceClException;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.get.GetRequest;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.support.master.AcknowledgedResponse;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.client.IndicesClient;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.indices.GetIndexRequest;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.index.query.*;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilder;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;

import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * es工具类
 *
 * @author
 * @date 2021/8/13 16:36
 */
public class ElasticSearchUtil {


    private static final String ERROR_NUM = "500";
    private static final String PARAMETER_NULL = "参数为空";

    private ElasticSearchUtil() {
    }


    /**
     * 需求: 假如现在数据库的数据量太多了,需要把数据导入es.使用es来进行查询
     * 分析: 1创建索引,2循环批量导入数据
     * PUT  goods
     * {
     *   "mappings": {
     *     "properties": {
     *       "title":{
     *         "type": "text"
     *       },
     *       "price":{
     *         "type": "integer"
     *       },
     *       "brandName":{
     *         "type": "keyword"
     *       }
     *     }
     *   }
     * }
     *
     * 假如数据库的有个字段是json串存储的,我们也需要存储到es里面去,此时我们需要这样处理: 不然会操作成功,但是json串表示的字段没有数据;
     * 假如字段title存的是json串,我们可以用  String titleSrt 来接受,然后我们在使用一个Map title 来接受titleStr转换的,
     * 这样最map就能表示json串,es的title字段就是json串;
     *
     *
     *            原生的es查询一般就是2个步骤: (原生的es在searchRequest对象里面可以同时构建查询多个索引,在springboot封装提供的里面没发现可以操作多索引的)
     *            第一: 客户端发送请求 searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
     *            第二: 构建 searchRequest对象
     *
     *
     *
     *
     *       termQuery: 词条查询,不分词
     *       matchQuery:  对词条进行分词,然后将分词的词条与结果进行等职查询, 默认取得是并集,也可以取交集(operator)
     *
     *
     *  查看分词
     *       POST /user/_analyze
     * {
     *   "field": "name",
     *   "text": ["陈英俊"]
     * }
     *
     *
     * 重构索引: 随着业务,有时候索引需要改变,es不允许我们改变索引, 这时候我们需要创建新的索引.将原有的索引的数据导入进去
     *
     */

    /**
     * 根据索引名判断索引是否存在
     *
     * @param restHighLevelClient 高级客户端对象
     * @param indexName           索引名
     * @return 结果
     */
    public static Boolean checkIndexExist(RestHighLevelClient restHighLevelClient, String indexName) {
        if (restHighLevelClient == null || StringUtils.isEmpty(indexName))
            throw new ServiceClException(ERROR_NUM, PARAMETER_NULL);
        // 获得操作索引对象
        IndicesClient indices = restHighLevelClient.indices();
        GetIndexRequest getIndexRequest = new GetIndexRequest(indexName);
        boolean result = false;
        try {
            result = indices.exists(getIndexRequest, RequestOptions.DEFAULT);
        } catch (IOException e) {
            throw new ServiceClException(ERROR_NUM, e.getMessage());
        }
        return result;
    }

    /**
     * 删除索引
     *
     * @param restHighLevelClient 高级客户端
     * @param indexName           索引名
     * @return
     */
    public static Boolean deleteIndex(RestHighLevelClient restHighLevelClient, String indexName) {
        if (restHighLevelClient == null || StringUtils.isEmpty(indexName))
            throw new ServiceClException(ERROR_NUM, PARAMETER_NULL);
        IndicesClient indices = restHighLevelClient.indices();
        DeleteIndexRequest deleteIndexRequest = new DeleteIndexRequest(indexName);
        AcknowledgedResponse acknowledgedResponse = null;
        try {
            acknowledgedResponse = indices.delete(deleteIndexRequest, RequestOptions.DEFAULT);
        } catch (IOException e) {
            throw new ServiceClException(ERROR_NUM, e.getMessage());
        }
        return acknowledgedResponse.isAcknowledged();
    }

    /**
     * 添加文档,从map获取数据
     * 文档都是基于索引去操作的
     *
     * @param restHighLevelClient
     * @param indexName
     * @param id
     */
    public static void addDocumentWithMap(RestHighLevelClient restHighLevelClient, String indexName, String id) {
        // 操作文档是通过索引操作文档
        HashMap<String, String> dataMap = new HashMap<>();
        dataMap.put("字段名", "字段值");
        IndexRequest indexRequest = new IndexRequest(indexName).id(id).source(dataMap);
        try {
            IndexResponse indexResponse = restHighLevelClient.index(indexRequest, RequestOptions.DEFAULT);
        } catch (IOException e) {
            throw new ServiceClException(ERROR_NUM, e.getMessage());
        }
    }

    /**
     * 添加文档
     *
     * @param restHighLevelClient
     * @param indexName           基于索引操作的文档
     */
//    public static void addDocumentWithObj(RestHighLevelClient restHighLevelClient, String indexName) {
//        // 操作文档是通过索引操作文档
//        User user = new User();
//        user.setId("a");
//        user.setName("a");
//        user.setAge(12);
//        user.setAddress("a");
//        user.setWeapon("a"); // 此处尝试修改为list,报错
//        String data = JSON.toJSONString(user);
//
//        IndexRequest indexRequest = new IndexRequest(indexName).id(user.getId()).source(data, XContentType.JSON);
//        try {
//            IndexResponse indexResponse = restHighLevelClient.index(indexRequest, RequestOptions.DEFAULT);
//        } catch (IOException e) {
//            throw new ServiceClException(ERROR_NUM, e.getMessage());
//        }
//    }

    /**
     * id存在就是修改,id不存在就是添加
     *
     * @param restHighLevelClient
     * @param indexName
     */
//    public static void updateDocument(RestHighLevelClient restHighLevelClient, String indexName) {
//        // 操作文档是通过索引操作文档
//        User user = new User();
//        user.setId("a");
//        user.setName("a1");
//        user.setAge(12);
//        user.setAddress("a1");
//        user.setWeapon("a1"); // 此处尝试修改为list,报错
//        String data = JSON.toJSONString(user);
//
//        IndexRequest indexRequest = new IndexRequest(indexName).id(user.getId()).source(data, XContentType.JSON);
//        try {
//            restHighLevelClient.index(indexRequest, RequestOptions.DEFAULT);
//        } catch (IOException e) {
//            throw new ServiceClException(ERROR_NUM, e.getMessage());
//        }
//    }

    /**
     * 通过指定的索引和id查询文档
     *
     * @param restHighLevelClient
     * @param indexName
     * @param id
     * @return
     */
    public static String findDocumentById(RestHighLevelClient restHighLevelClient, String indexName, String id) {

        GetRequest getIndexRequest = new GetRequest(indexName, id);
        try {
            GetResponse response = restHighLevelClient.get(getIndexRequest, RequestOptions.DEFAULT);
            return response.getSourceAsString();
        } catch (IOException e) {
            throw new ServiceClException(ERROR_NUM, e.getMessage());
        }
    }

    /**
     * 通过索引和id删除文档
     *
     * @param restHighLevelClient
     * @param indexName
     * @param id
     */
    public static void deleteDocumentById(RestHighLevelClient restHighLevelClient, String indexName, String id) {
        DeleteRequest deleteRequest = new DeleteRequest(indexName, id);
        try {
            restHighLevelClient.delete(deleteRequest, RequestOptions.DEFAULT);
        } catch (IOException e) {
            throw new ServiceClException(ERROR_NUM, e.getMessage());
        }
    }

    /**
     * 批量操作: 减少交互,资源的消耗
     */

    public static void bulk(RestHighLevelClient restHighLevelClient) {

        /**
         * 批量添加: 可以批量操作了
         *
         */
        // 创建整合批量操作对象
        BulkRequest bulkRequest = new BulkRequest();
        // 添加操作
        DeleteRequest deleteRequest = new DeleteRequest("索引名称", "操作id");// 删除
        bulkRequest.add(deleteRequest);
        HashMap<String, String> dataMap = new HashMap<>();
        dataMap.put("字段名", "字段值");
        IndexRequest indexRequest = new IndexRequest("索引名称").id("操作id").source(dataMap);// 添加
        bulkRequest.add(indexRequest);
        HashMap<String, String> dataMap1 = new HashMap<>();
        dataMap.put("字段名", "被修改的字段值");
        UpdateRequest updateRequest = new UpdateRequest("索引名称", "操作id").doc(dataMap1);
        bulkRequest.add(updateRequest);
        // 执行批量
        try {
            BulkResponse responses = restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
            RestStatus status = responses.status();// 成功status返回的是"ok"
        } catch (IOException e) {
            throw new ServiceClException(ERROR_NUM, e.getMessage());
        }
    }


    public static void matchAllQuery(RestHighLevelClient restHighLevelClient) {

        /**
         * 其实就是2个步骤:
         *  第一: 客户端发送请求 searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
         *  第二: 构建 searchRequest对象
         */
        // 2 构建查询请求对象,指定查询的索引名
        SearchRequest searchRequest = new SearchRequest("指定查询的索引名称");
        // 4 创建条件查询构建器
        SearchSourceBuilder searchSourceBuild = new SearchSourceBuilder();
        // 6 确认真正的查询条件
        QueryBuilder query = QueryBuilders.matchAllQuery();
        // 5 指定查询条件
        searchSourceBuild.query(query);
        // 9 封装分页
        searchSourceBuild.from(0);
        searchSourceBuild.size(100);
        // 3 添加条件查询构建器物
        searchRequest.source(searchSourceBuild);
        // 1 客户端进行查询,得到查询结果
        SearchResponse searchResponse = null;
        try {
            searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        } catch (IOException e) {
            throw new ServiceClException(ERROR_NUM, e.getMessage());
        }
        // 7 获取命中对象
        SearchHits searchHits = searchResponse.getHits();
        // 7.1 获取总记录数 (这个总记录数只是es查询的总数,返回的结果集的总数是按照分页数返回的;)
        long hitsCount = searchHits.getTotalHits().value;
        // 8 获取查询数据
        SearchHit[] hits = searchHits.getHits();
        List<String> list = Arrays.stream(hits).map(SearchHit::getSourceAsString).collect(Collectors.toList());

    }

    /**
     * 词条不分词等值查询
     *
     * @param restHighLevelClient
     */
    public static void termQuery(RestHighLevelClient restHighLevelClient) {


        // 2 构建查询请求对象,指定查询的索引名
        SearchRequest searchRequest = new SearchRequest("指定查询的索引名称");
        // 4 创建条件查询构建器
        SearchSourceBuilder searchSourceBuild = new SearchSourceBuilder();
        // 6 确认真正的查询条件
        QueryBuilder query = QueryBuilders.termQuery("字段", "字段值");
        // 5 指定查询条件
        searchSourceBuild.query(query);
        // 9 封装分页
        searchSourceBuild.from(0);
        searchSourceBuild.size(100);
        // 3 添加条件查询构建器物
        searchRequest.source(searchSourceBuild);
        // 1 客户端进行查询,得到查询结果
        SearchResponse searchResponse = null;
        try {
            searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        } catch (IOException e) {
            throw new ServiceClException(ERROR_NUM, e.getMessage());
        }
        // 7 获取命中对象
        SearchHits searchHits = searchResponse.getHits();
        // 7.1 获取总记录数 (这个总记录数只是es查询的总数,返回的结果集的总数是按照分页数返回的;)
        long hitsCount = searchHits.getTotalHits().value;
        // 8 获取查询数据
        SearchHit[] hits = searchHits.getHits();
        List<String> list = Arrays.stream(hits).map(SearchHit::getSourceAsString).collect(Collectors.toList());

    }


    /**
     * 词条分词后等值查询  默认取分词后查询出来的并集
     *
     * @param restHighLevelClient
     */
    public static void matchQuery(RestHighLevelClient restHighLevelClient) {


        // 2 构建查询请求对象,指定查询的索引名
        SearchRequest searchRequest = new SearchRequest("指定查询的索引名称");
        // 4 创建条件查询构建器
        SearchSourceBuilder searchSourceBuild = new SearchSourceBuilder();
        // 6 确认真正的查询条件
        QueryBuilder query = QueryBuilders.matchQuery("字段", "value");
        // 修改数据重合方式 并集变为交集
        ((MatchQueryBuilder) query).operator(Operator.AND);
        // 5 指定查询条件
        searchSourceBuild.query(query);
        // 9 封装分页
        searchSourceBuild.from(0);
        searchSourceBuild.size(100);
        // 3 添加条件查询构建器物
        searchRequest.source(searchSourceBuild);
        // 1 客户端进行查询,得到查询结果
        SearchResponse searchResponse = null;
        try {
            searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        } catch (IOException e) {
            throw new ServiceClException(ERROR_NUM, e.getMessage());
        }
        // 7 获取命中对象
        SearchHits searchHits = searchResponse.getHits();
        // 7.1 获取总记录数 (这个总记录数只是es查询的总数,返回的结果集的总数是按照分页数返回的;)
        long hitsCount = searchHits.getTotalHits().value;
        // 8 获取查询数据
        SearchHit[] hits = searchHits.getHits();
        List<String> list = Arrays.stream(hits).map(SearchHit::getSourceAsString).collect(Collectors.toList());

    }


    /**
     * 词条分词后 通配符模糊匹配查询
     * ? 表示任意单个字符  * 表示0个或多个字符
     * 词条分词后 正则模糊匹配查询  regexp
     * 词条分词后 前缀模糊匹配查询  prefix
     *
     * @param restHighLevelClient
     */
    public static void wildcardQuery(RestHighLevelClient restHighLevelClient) {


        // 2 构建查询请求对象,指定查询的索引名
        SearchRequest searchRequest = new SearchRequest("指定查询的索引名称");
        // 4 创建条件查询构建器
        SearchSourceBuilder searchSourceBuild = new SearchSourceBuilder();
        // 6 确认真正的查询条件
        QueryBuilder query = QueryBuilders.wildcardQuery("字段", "value");

        // 5 指定查询条件
        searchSourceBuild.query(query);
        // 9 封装分页
        searchSourceBuild.from(0);
        searchSourceBuild.size(100);
        // 10 排序
        searchSourceBuild.sort("字段名", SortOrder.DESC);
        // 3 添加条件查询构建器物
        searchRequest.source(searchSourceBuild);
        // 1 客户端进行查询,得到查询结果
        SearchResponse searchResponse = null;
        try {
            searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        } catch (IOException e) {
            throw new ServiceClException(ERROR_NUM, e.getMessage());
        }
        // 7 获取命中对象
        SearchHits searchHits = searchResponse.getHits();
        // 7.1 获取总记录数 (这个总记录数只是es查询的总数,返回的结果集的总数是按照分页数返回的;)
        long hitsCount = searchHits.getTotalHits().value;
        // 8 获取查询数据
        SearchHit[] hits = searchHits.getHits();
        List<String> list = Arrays.stream(hits).map(SearchHit::getSourceAsString).collect(Collectors.toList());

    }

    /**
     * 将查询条件进行分词,分词后的查询条件和词条进行等值匹配,默认取并集,可以指定多个字段查询(一个条件可以对多个字段进行查询)
     * queryString 和 simpleQueryString:
     * 前者对查询条件之间可以添加 or and  后者不可以添加,即使添加了也没有效果
     *
     * @param restHighLevelClient
     */
    public static void queryString(RestHighLevelClient restHighLevelClient) {


        // 2 构建查询请求对象,指定查询的索引名
        SearchRequest searchRequest = new SearchRequest("指定查询的索引名称");
        // 4 创建条件查询构建器
        SearchSourceBuilder searchSourceBuild = new SearchSourceBuilder();
        // 6 确认真正的查询条件
        QueryBuilder query = QueryBuilders.queryStringQuery("查询条件值").field("字段1").field("字段2").defaultOperator(Operator.AND);

        // 5 指定查询条件
        searchSourceBuild.query(query);
        // 9 封装分页
        searchSourceBuild.from(0);
        searchSourceBuild.size(100);
        // 10 排序
        searchSourceBuild.sort("字段名", SortOrder.DESC);
        // 3 添加条件查询构建器物
        searchRequest.source(searchSourceBuild);
        // 1 客户端进行查询,得到查询结果
        SearchResponse searchResponse = null;
        try {
            searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        } catch (IOException e) {
            throw new ServiceClException(ERROR_NUM, e.getMessage());
        }
        // 7 获取命中对象
        SearchHits searchHits = searchResponse.getHits();
        // 7.1 获取总记录数 (这个总记录数只是es查询的总数,返回的结果集的总数是按照分页数返回的;)
        long hitsCount = searchHits.getTotalHits().value;
        // 8 获取查询数据
        SearchHit[] hits = searchHits.getHits();
        List<String> list = Arrays.stream(hits).map(SearchHit::getSourceAsString).collect(Collectors.toList());

    }


    /**
     * 聚合查询:
     * 指标聚合: 相当于mysql的 max avg sum等
     * 桶聚合: 相当于mysql的group by  不要对text类型的数据进行分组,会失败的
     */


    /**
     * 桶聚合 demo
     * 需求:
     * 查询title包含手机的数据
     * 查询品牌列表
     *
     * @param restHighLevelClient
     */
    public static void aggQuery(RestHighLevelClient restHighLevelClient) {


        // 2 构建查询请求对象,指定查询的索引名
        SearchRequest searchRequest = new SearchRequest("指定查询的索引名称");
        // 4 创建条件查询构建器
        SearchSourceBuilder searchSourceBuild = new SearchSourceBuilder();
        // 6 确认真正的查询条件
        QueryBuilder query = QueryBuilders.matchQuery("字段", "字段值");
        // 5 指定查询条件
        searchSourceBuild.query(query);

        // 7 聚合
        AggregationBuilder agg = AggregationBuilders.terms("自定义用于获取数据的name").field("聚合的字段名");
        searchSourceBuild.aggregation(agg);

        // 3 添加条件查询构建器物
        searchRequest.source(searchSourceBuild);
        // 1 客户端进行查询,得到查询结果
        SearchResponse searchResponse = null;
        try {
            searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        } catch (IOException e) {
            throw new ServiceClException(ERROR_NUM, e.getMessage());
        }
        // 7 获取命中对象
        SearchHits searchHits = searchResponse.getHits();
        // 7.1 获取总记录数 (这个总记录数只是es查询的总数,返回的结果集的总数是按照分页数返回的;)
        long hitsCount = searchHits.getTotalHits().value;
        // 8 获取查询数据
        SearchHit[] hits = searchHits.getHits();
        List<String> list = Arrays.stream(hits).map(SearchHit::getSourceAsString).collect(Collectors.toList());

        // 获取聚合的结果集
        Aggregations aggregations = searchResponse.getAggregations();
        Map<String, Aggregation> map = aggregations.asMap();
        Terms aggregation = (Terms)map.get("自定义用于获取数据的name");
        List<? extends Terms.Bucket> buckets = aggregation.getBuckets();
        for (Terms.Bucket bucket : buckets) {
            System.out.println(bucket.getKey());
        }

    }


    /**
     * 高亮查询:
     * 高亮字段 前缀  后缀
     * 然后进行替换原有的值
     */
    public static void highLight(RestHighLevelClient restHighLevelClient) {


        // 2 构建查询请求对象,指定查询的索引名
        SearchRequest searchRequest = new SearchRequest("指定查询的索引名称");
        // 4 创建条件查询构建器
        SearchSourceBuilder searchSourceBuild = new SearchSourceBuilder();
        // 6 确认真正的查询条件
        QueryBuilder query = QueryBuilders.matchQuery("字段", "字段值");
        // 5 指定查询条件
        searchSourceBuild.query(query);
        // 设置高亮
        HighlightBuilder highlight =new HighlightBuilder();
        // 设置高亮三要素 (不设置就是默认的 <em> 前后缀)
        highlight.field("需要设置高亮的字段").preTags("前缀").postTags("后缀");
        searchSourceBuild.highlighter(highlight);
        // 9 封装分页
        searchSourceBuild.from(0);
        searchSourceBuild.size(100);
        // 10 排序
        searchSourceBuild.sort("字段名", SortOrder.DESC);
        // 3 添加条件查询构建器物
        searchRequest.source(searchSourceBuild);
        // 1 客户端进行查询,得到查询结果
        SearchResponse searchResponse = null;
        try {
            searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        } catch (IOException e) {
            throw new ServiceClException(ERROR_NUM, e.getMessage());
        }
        // 7 获取命中对象
        SearchHits searchHits = searchResponse.getHits();
        // 7.1 获取总记录数 (这个总记录数只是es查询的总数,返回的结果集的总数是按照分页数返回的;)
        long hitsCount = searchHits.getTotalHits().value;
        // 8 获取查询数据
        SearchHit[] hits = searchHits.getHits();
        for (SearchHit hit : hits) {
            Map<String, HighlightField> highlightFields = hit.getHighlightFields();
            HighlightField filed = highlightFields.get("需要设置高亮的字段");
            Text[] fragments = filed.fragments();// 片段
            // 替换
           // set进去
        }
        List<String> list = Arrays.stream(hits).map(SearchHit::getSourceAsString).collect(Collectors.toList());

    }


    // -----------------------------------------------------------------------------------------------------------------


    /**
     * 布尔查询: 多条件连接查询,十分常用
     * 连接方式:
     * must: and 条件不许成立
     * must_not:not 条件必须不成立
     * should: or 条件可以成立 至少满足一条
     * filter: 条件必须成立,性能比must高,不会计算得分
     */

    /**
     * term 等值词条查询
     *
     * @param field 查询字段
     * @param value 查询值
     * @param query QueryBuilder
     */
    public static void addEqual(String field, String value, BoolQueryBuilder query) {
        if (StringUtils.isNotEmpty(value)) {
            query.must(QueryBuilders.termQuery(field, value));
        }
    }

    /**
     * 大于等于
     *
     * @param field 查询字段
     * @param value 查询值
     * @param query QueryBuilder
     */
    public static void addGreaterThanEqual(String field, String value, BoolQueryBuilder query) {
        if (StringUtils.isNotEmpty(value)) {
            query.must(QueryBuilders.rangeQuery(field).gte(value));
        }
    }

    /**
     * 小于等于
     *
     * @param field 查询字段
     * @param value 查询值
     * @param query QueryBuilder
     */
    public static void addLessThanEqual(String field, String value, BoolQueryBuilder query) {
        if (StringUtils.isNotEmpty(value)) {
            query.must(QueryBuilders.rangeQuery(field).lte(value));
        }
    }

    /**
     * 模糊查询，类似sql的 like %value%
     *
     * @param field 查询字段
     * @param value 查询值
     * @param query QueryBuilder
     */
    public static void addLike(String field, String value, BoolQueryBuilder query) {
        if (StringUtils.isNotEmpty(value)) {
            query.must(QueryBuilders.wildcardQuery(field, "*" + value + "*"));
        }
    }

    /**
     * 字段值不能为null
     *
     * @param field 字段名称
     * @param query QueryBuilder
     */
    public static void addNotNull(String field, BoolQueryBuilder query) {
        query.must(QueryBuilders.existsQuery(field));
    }
}
